"""
RapidOCR API 服务
- 支持真正的多线程并发（无需信号量）
- 支持 NPU/CPU 部署
- 轻量级、高准确率
- 端口: 8110
"""

import uuid
import os
import time
import traceback
import sys
import asyncio
from pathlib import Path
from fastapi import FastAPI, UploadFile, File, HTTPException, Form
from fastapi.responses import JSONResponse
from typing import List, Optional
from PIL import Image, ImageEnhance
import numpy as np

# 导入 RapidOCR
try:
    from rapidocr_onnxruntime import RapidOCR
except ImportError as e:
    raise ImportError(
        "Failed to import rapidocr_onnxruntime. "
        "Please install: pip install rapidocr-onnxruntime\n"
        f"Original error: {e}"
    )

# 导入 PyMuPDF (fitz module) 用于 PDF 处理
try:
    import fitz  # PyMuPDF
except ImportError as e:
    raise ImportError(
        "Failed to import PyMuPDF (fitz module). "
        "Please ensure PyMuPDF is installed: pip install PyMuPDF\n"
        "If you have a conflicting 'fitz' package, uninstall it first: pip uninstall fitz\n"
        f"Original error: {e}"
    )
except Exception as e:
    if "Directory 'static/' does not exist" in str(e):
        raise ImportError(
            "Conflicting 'fitz' package detected. This is not PyMuPDF.\n"
            "Please uninstall the conflicting package and install PyMuPDF:\n"
            "  pip uninstall fitz\n"
            "  pip install PyMuPDF\n"
            f"Original error: {e}"
        )
    raise

# 初始化 FastAPI 应用
app = FastAPI(
    title="OCR识别API (RapidOCR)", 
    description="支持文件上传进行OCR识别，使用RapidOCR，支持真正的并发处理"
)


def check_onnx_providers():
    """
    检查 ONNX Runtime 可用的 Execution Providers
    
    Returns:
        list: 可用的提供者列表
    """
    try:
        import onnxruntime as ort
        providers = ort.get_available_providers()
        return providers
    except:
        return []


def get_actual_device_info(ocr_instance):
    """
    获取 RapidOCR 实际使用的设备信息
    
    Args:
        ocr_instance: RapidOCR 实例
    
    Returns:
        tuple: (设备信息字符串, 是否使用NPU的布尔值)
    """
    try:
        # RapidOCR 内部使用 ONNX Runtime
        # 尝试从内部模型获取 Provider 信息
        if hasattr(ocr_instance, 'text_det'):
            # 检测模型的 session
            if hasattr(ocr_instance.text_det, 'session'):
                providers = ocr_instance.text_det.session.get_providers()
                if providers:
                    actual_provider = providers[0]  # 第一个是实际使用的
                    if 'CANN' in actual_provider or 'NPU' in actual_provider:
                        return f"NPU ({actual_provider})", True
                    elif 'CUDA' in actual_provider or 'GPU' in actual_provider:
                        return f"GPU ({actual_provider})", False
                    else:
                        return f"CPU ({actual_provider})", False
        
        # 如果无法获取详细信息，返回通用信息
        available_providers = check_onnx_providers()
        if available_providers:
            first_provider = available_providers[0]
            if 'CANN' in first_provider or 'NPU' in first_provider:
                return f"NPU ({first_provider})", True
            elif 'CUDA' in first_provider or 'GPU' in first_provider:
                return f"GPU ({first_provider})", False
            else:
                return f"CPU ({first_provider})", False
        
        return "未知设备", False
    except:
        return "未知设备", False


def init_rapidocr():
    """
    初始化 RapidOCR 模型（强制使用 NPU）
    
    Returns:
        RapidOCR 实例
    """
    try:
        # 检查 ONNX Runtime 可用的 Execution Providers
        available_providers = check_onnx_providers()
        print("=" * 80)
        print("🔍 检查 ONNX Runtime Execution Providers:")
        if available_providers:
            for idx, provider in enumerate(available_providers, 1):
                marker = "✅" if idx == 1 else "  "
                print(f"  {marker} {provider}")
        else:
            print("  ⚠️ 无法检测 ONNX Runtime Providers")
        print("=" * 80)
        
        # 检查是否有 NPU 相关的 Provider
        npu_providers = [p for p in available_providers if 'NPU' in p.upper() or 'CANN' in p.upper()]
        
        if not npu_providers:
            # NPU 不可用，打印错误信息但继续运行（使用 CPU）
            print("❌ 未检测到 NPU Provider！")
            print("=" * 80)
            print("⚠️ 警告: NPU 不可用，将使用 CPU 运行（性能较低）")
            print("")
            print("NPU 环境配置要求:")
            print("  1. 安装昇腾 CANN 驱动")
            print("     - 从华为昇腾官网下载对应版本")
            print("     - 安装后配置环境变量 ASCEND_HOME")
            print("")
            print("  2. 卸载标准版 ONNX Runtime")
            print("     pip uninstall onnxruntime")
            print("")
            print("  3. 安装 NPU 版本 ONNX Runtime")
            print("     pip install onnxruntime-npu")
            print("")
            print("  4. 验证安装")
            print("     python file_parse/check_npu.py")
            print("=" * 80)
            
            # 继续使用 CPU
            ocr = RapidOCR()
            actual_device = "CPU (NPU 不可用)"
            npu_success = False
        else:
            # NPU 可用
            print(f"✅ 检测到 NPU Providers: {', '.join(npu_providers)}")
            ocr = RapidOCR()
            actual_device = f"NPU ({npu_providers[0]})"
            npu_success = True
        
        # 打印初始化结果
        print("=" * 80)
        if npu_success:
            print("✅ RapidOCR 模型初始化成功 - NPU 模式")
        else:
            print("⚠️ RapidOCR 模型初始化成功 - CPU 模式（NPU 未启用）")
        print(f"  - 引擎: ONNX Runtime")
        print(f"  - 实际设备: {actual_device}")
        print(f"  - NPU 状态: {'✅ 已启用' if npu_success else '❌ 未启用'}")
        print(f"  - 并发支持: ✅ 线程安全")
        print("=" * 80)
        
        return ocr
        
    except Exception as e:
        error_msg = f"RapidOCR 模型初始化失败: {str(e)}"
        print("=" * 80)
        print(error_msg)
        print(traceback.format_exc())
        print("=" * 80)
        raise HTTPException(status_code=500, detail=error_msg)


def pdf_to_images(pdf_path, output_dir, dpi=200):
    """
    将PDF转换为图片列表
    
    Args:
        pdf_path: PDF文件路径
        output_dir: 输出目录
        dpi: 转换分辨率（默认200）
    
    Returns:
        tuple: (图片路径列表, 转换耗时(秒))
    """
    image_paths = []
    start_time = time.time()
    pdf_document = None
    try:
        pdf_document = fitz.open(pdf_path)
        total_pages = len(pdf_document)
        
        for page_num in range(total_pages):
            page = pdf_document[page_num]
            # 设置缩放比例（DPI）
            zoom = dpi / 72.0
            mat = fitz.Matrix(zoom, zoom)
            pix = page.get_pixmap(matrix=mat)

            # 保存为PNG图片
            image_path = output_dir / f"page_{page_num + 1}.png"
            pix.save(image_path)
            image_paths.append(image_path)
            
            # 显式释放 pixmap 内存
            pix = None
        
        elapsed_time = time.time() - start_time
        print("=" * 80)
        print(f"PDF转换完成:")
        print(f"  总页数: {total_pages}")
        print(f"  转换耗时: {elapsed_time:.2f} 秒 ({elapsed_time/60:.2f} 分钟)")
        print(f"  平均每页: {elapsed_time/total_pages:.2f} 秒" if total_pages > 0 else "")
        print("=" * 80)
        
        return image_paths, elapsed_time
    except Exception as e:
        elapsed_time = time.time() - start_time
        print(f"PDF转换失败 (耗时 {elapsed_time:.2f} 秒): {e}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=f"PDF转换失败: {str(e)}")
    finally:
        # 确保 PDF 文档被关闭释放内存
        if pdf_document is not None:
            pdf_document.close()


def preprocess_image(image_path, max_size=(4000, 4000), max_megapixels=16, enable_compression=False):
    """
    预处理图片：根据配置决定是否压缩图片
    
    Args:
        image_path: 图片路径
        max_size: 最大尺寸 (width, height)
        max_megapixels: 最大百万像素数（16MP = 约4000x4000）
        enable_compression: 压缩控制参数
            - True: 强制压缩所有图片
            - False: 只有图片过大时才自动压缩（默认）
    
    Returns:
        tuple: (处理后的图片路径, 是否压缩, 原始文件大小(字节), 压缩后文件大小(字节))
    """
    try:
        original_size = image_path.stat().st_size if image_path.exists() else 0
        
        with Image.open(image_path) as img:
            width, height = img.size
            megapixels = (width * height) / 1_000_000

            # 判断是否需要压缩
            need_compress = False
            if enable_compression:
                need_compress = True
            else:
                if megapixels > max_megapixels or width > max_size[0] or height > max_size[1]:
                    need_compress = True
            
            if need_compress:
                # 计算缩放比例
                scale = min(max_size[0] / width, max_size[1] / height,
                            (max_megapixels * 1_000_000 / (width * height)) ** 0.5)
                new_width = int(width * scale)
                new_height = int(height * scale)

                # 压缩图片
                img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)

                # 保存压缩后的图片
                processed_path = image_path.parent / f"{image_path.stem}_processed{image_path.suffix}"
                img_resized.save(processed_path, quality=95, optimize=True)
                
                compressed_size = processed_path.stat().st_size if processed_path.exists() else 0

                return processed_path, True, original_size, compressed_size
            else:
                return image_path, False, original_size, original_size
    except Exception as e:
        print(f"图片预处理失败，使用原图: {e}")
        original_size = image_path.stat().st_size if image_path.exists() else 0
        return image_path, False, original_size, original_size


def perform_ocr_with_rapidocr(image_path, ocr_instance=None, show_device_info=False):
    """
    使用 RapidOCR 执行 OCR 识别
    
    Args:
        image_path: 图片路径
        ocr_instance: RapidOCR 实例（可选，如果不提供则创建新实例）
        show_device_info: 是否显示设备信息（默认 False）
    
    Returns:
        dict: 包含识别文本和详细信息的字典
    """
    try:
        # 如果没有提供实例，创建新的（RapidOCR 创建成本低）
        if ocr_instance is None:
            ocr_instance = RapidOCR()
        
        # 检查并显示实际使用的设备
        if show_device_info:
            actual_device, is_using_npu = get_actual_device_info(ocr_instance)
            if is_using_npu:
                print(f"✅ [NPU 检查] 当前正在使用 NPU 识别图片: {actual_device}")
            else:
                print(f"❌ [NPU 检查] 当前未使用 NPU，正在使用: {actual_device}")
                print(f"   ⚠️ 性能可能受限，建议配置 NPU 环境")
        
        # RapidOCR 识别
        # 返回: (result, elapse_time)
        # result: [[[x1,y1],[x2,y2],[x3,y3],[x4,y4]], text, score]
        result, elapse = ocr_instance(str(image_path))
        
        if result is None:
            return {
                'texts': [],
                'full_text': '',
                'detailed_results': [],
                'text_count': 0,
                'elapse_time': 0
            }
        
        # 提取文本和详细信息
        texts = []
        detailed_results = []
        
        for item in result:
            bbox, text, score = item
            texts.append(text)
            
            # 保存详细信息
            detailed_results.append({
                'text': text,
                'confidence': float(score),
                'bbox': {
                    'points': bbox  # 四个点坐标 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
                }
            })
        
        # 拼接所有文本
        full_text = '\n'.join(texts)
        
        return {
            'texts': texts,
            'full_text': full_text,
            'detailed_results': detailed_results,
            'text_count': len(texts),
            'elapse_time': elapse
        }
    
    except Exception as e:
        raise Exception(f"RapidOCR 识别失败: {str(e)}")


def process_single_file_ocr(file_content: bytes, filename: str, file_id: str = None, enable_compression: bool = False):
    """
    处理单个文件的OCR识别（使用 RapidOCR）
    
    Args:
        file_content: 文件内容（字节）
        filename: 文件名
        file_id: 文件ID（可选，如果不提供则自动生成）
        enable_compression: 是否强制压缩图片
    
    Returns:
        dict: OCR识别结果
    """
    # 使用当前目录下的 tmp 文件夹
    base_dir = Path(__file__).parent.parent
    tmp_dir = base_dir / "tmp"
    tmp_dir.mkdir(exist_ok=True)

    # 生成唯一文件名
    if file_id is None:
        file_id = str(uuid.uuid4())
    file_ext = Path(filename).suffix.lower() if filename else ''
    input_file_path = tmp_dir / f"{file_id}{file_ext}"
    processed_file_paths = []
    was_compressed = False
    original_image_size = 0
    compressed_image_size = 0
    is_pdf = False
    pdf_pages = 0

    try:
        # 保存文件内容
        with open(input_file_path, "wb") as buffer:
            buffer.write(file_content)

        # 初始化 RapidOCR 模型（每次创建新实例，成本很低）
        ocr = init_rapidocr()

        # 判断文件类型并处理
        if file_ext == '.pdf':
            # PDF文件：转换为图片后逐页OCR
            is_pdf = True
            pdf_images_dir = tmp_dir / f"{file_id}_pages"
            pdf_images_dir.mkdir(exist_ok=True)

            # 将PDF转换为图片（记录时间）
            image_paths, pdf_conversion_time = pdf_to_images(input_file_path, pdf_images_dir)
            pdf_pages = len(image_paths)

            # 对所有页面进行OCR识别
            all_results = []
            all_texts = []
            page_results = []
            total_ocr_time = 0.0

            for page_idx, image_path in enumerate(image_paths):
                try:
                    # 预处理图片（压缩）
                    processed_path, compressed, orig_size, comp_size = preprocess_image(image_path, enable_compression=enable_compression)
                    if compressed:
                        processed_file_paths.append(processed_path)
                        if not was_compressed:
                            original_image_size = orig_size
                            compressed_image_size = comp_size
                            was_compressed = True
                    # 始终记录原始图片路径以便删除
                    processed_file_paths.append(image_path)

                    # 执行OCR识别（记录时间）
                    ocr_input_path = str(processed_path) if compressed else str(image_path)
                    page_ocr_start = time.time()
                    
                    ocr_result = perform_ocr_with_rapidocr(ocr_input_path, ocr)
                    
                    page_ocr_time = time.time() - page_ocr_start
                    total_ocr_time += page_ocr_time
                    
                    print(f"第 {page_idx + 1} 页OCR识别完成，耗时: {page_ocr_time:.2f} 秒")

                    # 保存结果
                    all_results.extend(ocr_result['detailed_results'])
                    all_texts.extend(ocr_result['texts'])
                    page_full_text = ocr_result['full_text']

                    # 记录每页的结果
                    page_results.append({
                        "page": page_idx + 1,
                        "texts": ocr_result['texts'],
                        "full_text": page_full_text,
                        "text_count": ocr_result['text_count'],
                        "ocr_time": page_ocr_time
                    })

                except Exception as e:
                    error_msg = f"第 {page_idx + 1} 页识别失败: {str(e)}"
                    print(error_msg)
                    print(traceback.format_exc())
                    page_results.append({
                        "page": page_idx + 1,
                        "error": error_msg
                    })

            # PDF处理完成后的总结
            full_text = "\n\n".join(all_texts)
            
            print("=" * 80)
            print(f"PDF文件OCR识别完成:")
            print(f"  总页数: {pdf_pages}")
            print(f"  PDF转换耗时: {pdf_conversion_time:.2f} 秒")
            print(f"  OCR识别耗时: {total_ocr_time:.2f} 秒")
            print(f"  总耗时: {(pdf_conversion_time + total_ocr_time):.2f} 秒")
            print("=" * 80)

        else:
            # 图片文件处理
            if file_ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp']:
                processed_file_path, was_compressed, original_image_size, compressed_image_size = preprocess_image(input_file_path, enable_compression=enable_compression)
                if was_compressed:
                    processed_file_paths.append(processed_file_path)
                ocr_input_path = str(processed_file_path) if was_compressed else str(input_file_path)
            else:
                ocr_input_path = str(input_file_path)

            # 执行 OCR 识别（显示设备信息）
            ocr_start_time = time.time()
            
            ocr_result = perform_ocr_with_rapidocr(ocr_input_path, ocr, show_device_info=True)
            
            ocr_time = time.time() - ocr_start_time
            print("=" * 80)
            print(f"图片OCR识别完成:")
            print(f"  识别耗时: {ocr_time:.2f} 秒")
            print(f"  识别文本数: {ocr_result['text_count']}")
            print("=" * 80)

            all_results = ocr_result['detailed_results']
            all_texts = ocr_result['texts']
            full_text = ocr_result['full_text']

        # 打印识别结果
        print("=" * 80)
        print("OCR识别结果 (full_text):")
        print("=" * 80)
        print(full_text)
        print("=" * 80)

        # 输出压缩信息
        if was_compressed:
            compression_ratio = (1 - compressed_image_size / original_image_size) * 100 if original_image_size > 0 else 0
            print(f"图片压缩信息:")
            print(f"  原始大小: {original_image_size / 1024:.2f} KB")
            print(f"  压缩后大小: {compressed_image_size / 1024:.2f} KB")
            print(f"  压缩率: {compression_ratio:.2f}%")

        # 返回结果
        response_data = {
            "status": "success",
            "file_id": file_id,
            "original_filename": filename,
            "file_type": "pdf" if is_pdf else "image",
            "ocr_engine": "RapidOCR",
            "results": all_results,
            "all_texts": all_texts,
            "full_text": full_text,
            "text_count": len(all_texts),
            "result_count": len(all_results),
            "image_compressed": was_compressed,
            "text_length": len(full_text)
        }

        # 如果压缩了图片，添加压缩信息到响应中
        if was_compressed:
            response_data["original_image_size"] = original_image_size
            response_data["compressed_image_size"] = compressed_image_size
            response_data["compression_ratio"] = round((1 - compressed_image_size / original_image_size) * 100, 2) if original_image_size > 0 else 0

        # PDF特有信息
        if is_pdf:
            response_data["pdf_pages"] = pdf_pages
            response_data["page_results"] = page_results
        
        return response_data

    finally:
        # 删除临时文件
        try:
            if input_file_path.exists():
                input_file_path.unlink()

            for file_path in processed_file_paths:
                if file_path and file_path.exists():
                    file_path.unlink()

            if is_pdf:
                pdf_images_dir = tmp_dir / f"{file_id}_pages"
                if pdf_images_dir.exists():
                    import shutil
                    shutil.rmtree(pdf_images_dir, ignore_errors=True)
        except Exception as e:
            print(f"删除临时文件失败: {e}")


async def process_single_file_async(content: bytes, filename: str, idx: int, total: int, enable_compression: bool = False):
    """
    异步处理单个文件的OCR识别（RapidOCR 支持真正的并发）
    
    Args:
        content: 文件内容（字节）
        filename: 文件名
        idx: 文件序号
        total: 文件总数
        enable_compression: 是否强制压缩图片
    
    Returns:
        str: OCR识别的文本结果或错误信息
    """
    file_start_time = time.time()
    print(f"\n[并发任务 {idx}/{total}] 开始处理: {filename}")
    
    try:
        # RapidOCR 支持真正的并发，无需信号量保护
        # 直接使用 asyncio.to_thread 将同步函数转换为异步执行
        result = await asyncio.to_thread(
            process_single_file_ocr,
            content,
            filename,
            None,  # file_id
            enable_compression
        )
        
        file_time = time.time() - file_start_time
        print(f"[并发任务 {idx}/{total}] 文件 {filename} 处理完成，耗时: {file_time:.2f} 秒")
        
        return result["full_text"]
    
    except Exception as e:
        error_msg = str(e)
        file_time = time.time() - file_start_time
        print(f"[并发任务 {idx}/{total}] 文件 {filename} 处理失败 (耗时 {file_time:.2f} 秒): {error_msg}")
        print(traceback.format_exc())
        return f"[处理失败: {filename}]"


@app.get("/")
async def root():
    """根路径，返回 API 信息"""
    return {
        "message": "OCR识别API (RapidOCR)",
        "status": "running",
        "version": "RapidOCR",
        "ocr_engine": "RapidOCR",
        "concurrent_support": True,
        "features": [
            "轻量级 (~20MB)",
            "多线程并发 (无需信号量)",
            "高准确率 (90-95%)",
            "NPU/CPU 支持"
        ]
    }


@app.post("/ocr")
async def upload_and_ocr(
    file: UploadFile = File(...),
    enable_compression: bool = Form(False)
):
    """
    上传文件并进行OCR识别（支持图片和PDF扫描件）
    
    Args:
        file: 上传的文件（支持图片和PDF）
        enable_compression: 是否强制压缩图片
    
    Returns:
        JSONResponse: 包含OCR识别结果的响应
    """
    try:
        content = await file.read()
        
        # 调用核心处理函数
        result = process_single_file_ocr(content, file.filename, enable_compression=enable_compression)
        
        # 返回full_text字段（保持原有行为）
        return JSONResponse(result["full_text"])
    
    except Exception as e:
        error_msg = f"OCR识别失败: {str(e)}"
        print(error_msg)
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=error_msg)


@app.post("/ocr_batch")
async def upload_and_ocr_batch(
    files: List[UploadFile] = File(...),
    enable_compression: bool = Form(False)
):
    """
    批量上传文件并进行OCR识别（支持图片和PDF扫描件）- 支持真正的并发处理
    
    本接口采用真正的并发处理模式：
    - RapidOCR 是线程安全的，支持真正的并发
    - 无需信号量限制，所有任务可以同时执行
    - 处理速度是顺序处理的 N 倍（N = 并发数）
    - 使用线程池执行OCR任务，不阻塞事件循环
    
    注意：与 PaddleOCR 不同，RapidOCR 支持真正的并发处理，不需要任务排队。

    Args:
        files: 上传的文件列表（支持图片和PDF）
        enable_compression: 是否强制压缩图片
    
    Returns:
        JSONResponse: 包含所有文件OCR识别结果的列表
    """
    batch_start_time = time.time()
    
    print("=" * 80)
    print(f"批量OCR处理开始，共 {len(files)} 个文件")
    print(f"处理模式: 真正的并发（RapidOCR 线程安全）")
    print(f"压缩模式: {'强制压缩' if enable_compression else '自动压缩'}")
    print("=" * 80)
    
    # 准备所有任务
    tasks = []
    for idx, file in enumerate(files, 1):
        content = await file.read()
        
        # 创建异步任务（无需延迟，真正的并发）
        task = process_single_file_async(content, file.filename, idx, len(files), enable_compression)
        tasks.append(task)
    
    # 并发执行所有任务（真正的并发，无需排队）
    results = await asyncio.gather(*tasks)
    
    batch_time = time.time() - batch_start_time
    
    # 打印批量处理统计信息
    print("=" * 80)
    print(f"批量OCR处理完成:")
    print(f"  成功: {sum(1 for r in results if not r.startswith('[处理失败'))}")
    print(f"  失败: {sum(1 for r in results if r.startswith('[处理失败'))}")
    print(f"  总耗时: {batch_time:.2f} 秒 ({batch_time/60:.2f} 分钟)")
    print(f"  平均每个文件: {batch_time/len(files):.2f} 秒" if len(files) > 0 else "")
    print("=" * 80)
    
    return JSONResponse(results)


if __name__ == "__main__":
    import uvicorn
    import argparse
    
    # 解析命令行参数（仅端口和地址，固定使用 NPU）
    parser = argparse.ArgumentParser(
        description='RapidOCR API 服务 (NPU 专用版)',
        epilog='本服务专门针对昇腾 NPU 优化，自动检测并使用 NPU 进行推理'
    )
    parser.add_argument(
        '--port',
        type=int,
        default=8868,
        help='服务端口（默认: 8868）'
    )
    parser.add_argument(
        '--host',
        type=str,
        default='0.0.0.0',
        help='服务地址（默认: 0.0.0.0）'
    )
    
    args = parser.parse_args()

    print("=" * 80)
    print("🚀 启动 RapidOCR API 服务 (NPU 专用版)")
    print("=" * 80)
    print("配置:")
    print(f"  • 运行模式: NPU 优先（自动检测）")
    print(f"  • 服务地址: http://{args.host}:{args.port}")
    print("")
    print("特性:")
    print("  ✓ 昇腾 NPU 加速（自动检测）")
    print("  ✓ 真正的多线程并发（无需信号量）")
    print("  ✓ 轻量级（~20MB）")
    print("  ✓ 高准确率（中文 90-95%）")
    print("  ✓ 基于 ONNX Runtime + CANN")
    print("")
    print("注意:")
    print("  - NPU 可用: 自动使用 NPU 加速推理")
    print("  - NPU 不可用: 自动降级到 CPU（会有警告）")
    print("=" * 80)
    
    uvicorn.run(app, host=args.host, port=args.port)
